#include "StdAfx.h"
#include <MyControls/ThumbLine.h>
#include <MyControls/Timeline.h>
#include <MyControls/App.h>
#include <MyControls/Resource.h>
#include <MyFC/Utility.h>
#include <cmath>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

namespace My {

// Control

ThumbLine::Control::Control(ThumbLine & t) 
	: m_ThumbLine(t), m_Timeline(t.m_Timeline)
{
	m_ThumbLine.Add(this);
}

bool ThumbLine::Control::Visible() const
{
	return !m_Timeline.IsEmpty();
}

// LeftBracket

CRect ThumbLine::LeftBracket::Bounds() const
{
	return AlignRect(
		m_ThumbLine.m_WALeft.Bounds(),
		CPoint(m_Timeline.TimeToX(m_Timeline.GetWorkArea().first), 3),
		ALIGN_HRIGHT|ALIGN_VTOP);
}

String ThumbLine::LeftBracket::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_WORK_AREA_START);
}

bool ThumbLine::LeftBracket::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	if (m_Timeline.IsEmpty())
		return false;
	m_StartPos = ClientToParent(p0).x;
	m_StartValue = m_Timeline.TimeToX(m_Timeline.GetWorkArea().first);
	return true;
}

void ThumbLine::LeftBracket::HandleDragEnd(unsigned int flags, const CPoint & p)
{
	m_Timeline.UpdateWorkArea();
	m_Timeline.OnAutoScroll(false);
	m_Timeline.SetTimeAutoScroll(0);
}

void ThumbLine::LeftBracket::HandleDragging(unsigned int flags, const CPoint & p)
{
	int x = m_StartValue + ClientToParent(p).x - m_StartPos;
	int t = round(m_Timeline.XToTime(x));
	Span wa = m_Timeline.GetWorkArea();
	Span ts = m_Timeline.GetSequence()->GetTimeSpan();
	if (t > wa.last - 1)
		t = wa.last - 1;
	if (t < ts.first)
		t = ts.first;
	wa.first = t;
	m_Timeline.SetWorkArea(wa);
	m_Timeline.Update();

	// Autoscroll?
	x -= m_StartValue - m_StartPos;
	if (x < m_Timeline.GetGap())
		m_Timeline.SetTimeAutoScroll(x - m_Timeline.GetGap());
	else if (x > m_ThumbLine.Width() - m_Timeline.GetGap())
		m_Timeline.SetTimeAutoScroll(x - m_ThumbLine.Width() + m_Timeline.GetGap());
	else
		m_Timeline.SetTimeAutoScroll(0);
}

// RightBracket

CRect ThumbLine::RightBracket::Bounds() const
{
	return AlignRect(
		m_ThumbLine.m_WARight.Bounds(),
		CPoint(m_Timeline.TimeToX(m_Timeline.GetWorkArea().last), 3),
		ALIGN_HLEFT|ALIGN_VTOP);
}

String ThumbLine::RightBracket::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_WORK_AREA_END);
}

bool ThumbLine::RightBracket::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	if (m_Timeline.IsEmpty())
		return false;
	m_StartPos = ClientToParent(p0).x;
	m_StartValue = m_Timeline.TimeToX(m_Timeline.GetWorkArea().last);
	return true;
}

void ThumbLine::RightBracket::HandleDragEnd(unsigned int flags, const CPoint & p)
{
	m_Timeline.UpdateWorkArea();
	m_Timeline.OnAutoScroll(false);
	m_Timeline.SetTimeAutoScroll(0);
}

void ThumbLine::RightBracket::HandleDragging(unsigned int flags, const CPoint & p)
{
	int x = m_StartValue + ClientToParent(p).x - m_StartPos;
	int t = round(m_Timeline.XToTime(x));
	Span wa = m_Timeline.GetWorkArea();
	Span ts = m_Timeline.GetSequence()->GetTimeSpan();
	if (t < wa.first + 1)
		t = wa.first + 1;
	if (t > ts.last)
		t = ts.last;
	wa.last = t;
	m_Timeline.SetWorkArea(wa);
	m_Timeline.Update();

	// Autoscroll?
	x -= m_StartValue - m_StartPos;
	if (x < m_Timeline.GetGap())
		m_Timeline.SetTimeAutoScroll(x - m_Timeline.GetGap());
	else if (x > m_ThumbLine.Width() - m_Timeline.GetGap())
		m_Timeline.SetTimeAutoScroll(x - m_ThumbLine.Width() + m_Timeline.GetGap());
	else
		m_Timeline.SetTimeAutoScroll(0);
}

// Grip

CRect ThumbLine::Grip::Bounds() const
{
	Span wa = m_Timeline.GetWorkArea();
	return AlignRect(
		m_ThumbLine.m_WAThumb.Bounds(),
		CPoint(m_Timeline.TimeToX(0.5 * (wa.first + wa.last)), 4),
		ALIGN_HCENTER|ALIGN_VTOP);
}

String ThumbLine::Grip::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_WORK_AREA);
}

bool ThumbLine::Grip::Visible() const
{
	if (m_Timeline.IsEmpty())
		return false;
	Span wa = m_Timeline.GetWorkArea();
	int xfirst = m_Timeline.TimeToX(wa.first);
	int xlast = m_Timeline.TimeToX(wa.last);
	return (xlast - xfirst > m_ThumbLine.m_WAThumb.Size().cx + 4);
}

bool ThumbLine::Grip::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	if (m_Timeline.IsEmpty())
		return false;
	Span wa = m_Timeline.GetWorkArea();
	m_StartPos = ClientToParent(p0).x;
	m_Size = wa.Size();
	m_StartValue = m_Timeline.TimeToX(wa.first);
	return true;
}

void ThumbLine::Grip::HandleDragEnd(unsigned int flags, const CPoint & p)
{
	m_Timeline.UpdateWorkArea();
	m_Timeline.OnAutoScroll(false);
	m_Timeline.SetTimeAutoScroll(0);
}

void ThumbLine::Grip::HandleDragging(unsigned int flags, const CPoint & p)
{
	int x = m_StartValue + ClientToParent(p).x - m_StartPos;
	int min = m_Timeline.TimeToX(m_Timeline.GetSequence()->GetTimeSpan().first);
	int max = m_Timeline.TimeToX(m_Timeline.GetSequence()->GetTimeSpan().last - m_Size);
	if (x < min)
		x = min;
	if (x > max)
		x = max;
	int t = round(m_Timeline.XToTime(x));
	m_Timeline.SetWorkArea(Span(t, t + m_Size));
	m_Timeline.Update();

	// Autoscroll?
	x -= m_StartValue - m_StartPos;
	if (x < m_Timeline.GetGap())
		m_Timeline.SetTimeAutoScroll(x - m_Timeline.GetGap());
	else if (x > m_ThumbLine.Width() - m_Timeline.GetGap())
		m_Timeline.SetTimeAutoScroll(x - m_ThumbLine.Width() + m_Timeline.GetGap());
	else
		m_Timeline.SetTimeAutoScroll(0);
}

// Thumb

CRect ThumbLine::Thumb::Bounds() const
{
	return AlignRect(
		m_ThumbLine.m_ThumbGlyph.Bounds(),
		CPoint(m_Timeline.TimeToX(m_Timeline.GetCurrentFrame()), -1),
		ALIGN_HCENTER|ALIGN_VTOP);
}

String ThumbLine::Thumb::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_CURRENT_FRAME);
}

bool ThumbLine::Thumb::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	if (m_Timeline.IsEmpty())
		return false;
	m_StartPos = ClientToParent(p0).x;
	m_StartValue = m_Timeline.TimeToX(m_Timeline.GetCurrentFrame());
	return true;
}

void ThumbLine::Thumb::HandleDragEnd(unsigned int flags, const CPoint & p)
{
	m_Timeline.OnAutoScroll(false);
	m_Timeline.SetTimeAutoScroll(0);
	m_Timeline.UpdateCurrentFrame();
}

void ThumbLine::Thumb::HandleDragging(unsigned int flags, const CPoint & p)
{
	int x = m_StartValue + ClientToParent(p).x - m_StartPos;
	m_Timeline.SetCurrentFrame(round(m_Timeline.XToTime(x)));

	// Autoscroll?
	x -= m_StartValue - m_StartPos;

	if (x < m_Timeline.GetGap()) 
		m_Timeline.SetTimeAutoScroll(x - m_Timeline.GetGap());
	else if (x > m_ThumbLine.Width() - m_Timeline.GetGap())
		m_Timeline.SetTimeAutoScroll(x - m_ThumbLine.Width() + m_Timeline.GetGap());
	else 
		m_Timeline.SetTimeAutoScroll(0);
}

// ThumbLine

ThumbLine::ThumbLine(Timeline & t) : 
	m_Timeline(t),
	m_Grip(* this),
	m_LeftBracket(* this),
	m_RightBracket(* this),
	m_Thumb(* this)
{
	m_PaintOutside = true;

	m_ThumbGlyph.Load("Thumb", "ThumbMask");
	m_WALeft.Load("WALeft", "WALeftMask");
	m_WARight.Load("WARight", "WARightMask");
	m_WAThumb.Load("WAThumb");
}

String ThumbLine::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_WORK_AREA);
}

void ThumbLine::ProcessEvent(Event & e)
{
	if (e.Is<Sequence::PreviewFrameChange>())
		return;
	if (e.Is<Sequence::Event>() || e.Is<Timeline::Event>())
		Invalidate();
	else
		Container::ProcessEvent(e);
}

void ThumbLine::HandleLeftDown(unsigned int flags, const CPoint & p)
{
	if (!m_Timeline.IsEmpty())
		m_Timeline.SetCurrentFrame(round(m_Timeline.XToTime(p.x)));
	My::Container::HandleLeftDown(flags, p);
	// Simulate another click & drag:
	m_Thumb.HandleLeftDown(flags, p - m_Thumb.Offset());
	m_Thumb.HandleMouseMove(flags, p - m_Thumb.Offset());
}

void ThumbLine::HandleLeftUp(unsigned int flags, const CPoint & p)
{
	if (!m_Timeline.IsEmpty()) {
		m_Timeline.SetCurrentFrame(m_Timeline.GetSequence()->GetPreviewFrame());
		m_Timeline.UpdateCurrentFrame();
	}
	My::Container::HandleLeftUp(flags, p);
}

void ThumbLine::HandlePaint(CDC * pDC, const CRect & rect)
{
	CRect rClient = ClientRect();

	CRect rClip(rClient);
	rClip.top--;
	ClipRect clip(this, pDC, rClip);

	CBrush bFace; bFace.CreateSysColorBrush(COLOR_BTNFACE);
	CBrush bLight; bLight.CreateSysColorBrush(COLOR_BTNHILIGHT);
	CBrush bHalfLight(My::MeanSysColor(COLOR_BTNFACE, COLOR_BTNHILIGHT));

	CPen pBlack; pBlack.CreateStockObject(BLACK_PEN);
	CPen pLight(PS_SOLID, 1, GetSysColor(COLOR_BTNHILIGHT));
	CPen pHalfShadow(PS_SOLID, 1, My::MeanSysColor(COLOR_BTNFACE, COLOR_BTNSHADOW));

	pDC->SelectObject(&pBlack);

	CRect rRoller(rClient);
	rRoller.left += m_Timeline.GetGap();
	rRoller.right -= m_Timeline.GetGap();
	if (!m_Timeline.IsEmpty()) {
		Span s = m_Timeline.GetSequence()->GetTimeSpan();
		rRoller.left = m_Timeline.TimeToX(s.first);
		rRoller.right = m_Timeline.TimeToX(s.last);
	}

	pDC->MoveTo(rRoller.left, rRoller.top);
	pDC->LineTo(rRoller.right, rRoller.top);
	pDC->SelectObject(&pLight);
	pDC->MoveTo(rRoller.left, rRoller.top + 1);
	pDC->LineTo(rRoller.right, rRoller.top + 1);
	pDC->SelectObject(&pHalfShadow);
	pDC->MoveTo(rRoller.left, rRoller.top + 2);
	pDC->LineTo(rRoller.right, rRoller.top + 2);
	pDC->SelectObject(&pBlack);
	pDC->MoveTo(rRoller.left, rRoller.top + 3);
	pDC->LineTo(rRoller.right, rRoller.top + 3);

	if (m_Timeline.IsEmpty())
		return;

	CRect rRange(rClient);
	rRange.top += 4;
	Span s = m_Timeline.GetSequence()->GetTimeSpan();
	rRange.left = rClient.left + m_Timeline.TimeToX(s.first);
	rRange.right = rClient.left + m_Timeline.TimeToX(s.last);
	rRange.IntersectRect(&rRange, &rClient);
	pDC->FillRect(&rRange, &bLight);

	{
		CRect rClip(rClient);
		rClip.top += 3;
		ClipRect clip(this, pDC, rClip);

		CRect rWorkarea(rRange);
		Span wa = m_Timeline.GetWorkArea();
		rWorkarea.left = rClient.left + m_Timeline.TimeToX(wa.first);
		rWorkarea.right = rClient.left + m_Timeline.TimeToX(wa.last);
		pDC->FillRect(&rWorkarea, &bHalfLight);
		pDC->SelectObject(&pBlack);
		pDC->MoveTo(rWorkarea.left, rWorkarea.top - 1);
		pDC->LineTo(rWorkarea.right, rWorkarea.top - 1);

		if (wa != m_Timeline.GetSequence()->GetTimeSpan() && rWorkarea.Width() > m_WAThumb.Width() + 4)
			m_WAThumb.Draw(pDC, rWorkarea.CenterPoint().x, rClient.top + 4, ALIGN_HCENTER|ALIGN_VTOP);

		m_WALeft.Draw(pDC, rWorkarea.left, rClient.top + 3, ALIGN_HRIGHT|ALIGN_VTOP);
		m_WARight.Draw(pDC, rWorkarea.right, rClient.top + 3, ALIGN_HLEFT|ALIGN_VTOP);
	}

	m_ThumbGlyph.Draw(pDC, rClient.left + m_Timeline.TimeToX(m_Timeline.GetCurrentFrame()), rClient.top - 1, ALIGN_HCENTER|ALIGN_VTOP);
}

} // My